// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-11-13

package tracer

import (
	"context"
	"fmt"
	"gitee.com/go-libs/log/base"
	"gitee.com/go-libs/log/common"
	"gitee.com/go-libs/log/conf"
	"net/http"
	"sync"
	"time"
)

var (
	spanNilTime = time.Unix(0, 0)

	spanPool = &sync.Pool{
		New: func() any {
			return &span{}
		},
	}
)

// 链路跟踪.
type span struct {
	attr               common.Attr
	ctx                context.Context
	logs               []common.Item
	name               string
	startTime, endTime time.Time
	tracing            common.Tracing
}

// NewSpan
// 创建链路跟踪.
func NewSpan(format string, args ...any) common.Span {
	o := spanPool.Get().(*span).acquire()
	o.name = fmt.Sprintf(format, args...)
	o.tracing = common.NewTracing()
	return o.with(nil)
}

// NewSpanWithContext
// 基于 <context.Context> 创建链路跟踪.
func NewSpanWithContext(ctx context.Context, format string, args ...any) common.Span {
	if ctx != nil {
		o := spanPool.Get().(*span).acquire()
		o.name = fmt.Sprintf(format, args...)
		o.tracing = common.NewTracingWithContext(ctx)
		return o.with(ctx)
	}
	return NewSpan(format, args...)
}

// NewSpanWithHeader
// 基于 <http.Header> 创建链路跟踪.
func NewSpanWithHeader(header http.Header, format string, args ...any) common.Span {
	o := spanPool.Get().(*span).acquire()
	o.name = fmt.Sprintf(format, args...)
	o.tracing = common.NewTracingWithHeader(header)
	return o.with(nil)
}

// NewSpanWithRequest
// 基于 <http.Request> 创建链路跟踪.
func NewSpanWithRequest(request *http.Request) common.Span {
	o := spanPool.Get().(*span).acquire()
	o.name = request.URL.Path
	o.tracing = common.NewTracingWithHeader(request.Header)

	// Attention: do not use request.Context(), It may invoke async goroutine
	// cancelled.
	// return o.with(request.Context())
	return o.with(context.Background())
}

// +---------------------------------------------------------------------------+
// | Interface methods                                                         |
// +---------------------------------------------------------------------------+

// Child
// 创建子链路 <common.Span> 跟踪.
func (o *span) Child(format string, args ...any) common.Span {
	v := spanPool.Get().(*span).acquire()
	v.name = fmt.Sprintf(format, args...)
	v.tracing = o.tracing.Child()
	return v.with(v.ctx)
}

// End
// 结束链路跨度并提交.
func (o *span) End() {
	// 1. 结束跨度.
	o.endTime = time.Now()

	// 2. 上报跟踪.
	singleton.Send(o)
}

// Release
// 释放链路跟踪跨度.
func (o *span) Release() {
	o.release()
	spanPool.Put(o)
}

// +---------------------------------------------------------------------------+
// | Logging for span of a tracing                                             |
// +---------------------------------------------------------------------------+

func (o *span) Debug(format string, args ...any) {
	if conf.New().DebugOn() {
		o.log(base.LevelDebug, format, args...)
	}
}

func (o *span) Info(format string, args ...any) {
	if conf.New().InfoOn() {
		o.log(base.LevelInfo, format, args...)
	}
}

func (o *span) Warn(format string, args ...any) {
	if conf.New().WarnOn() {
		o.log(base.LevelWarn, format, args...)
	}
}

func (o *span) Error(format string, args ...any) {
	if conf.New().ErrorOn() {
		o.log(base.LevelError, format, args...)
	}
}

func (o *span) Fatal(format string, args ...any) {
	if conf.New().FatalOn() {
		o.log(base.LevelFatal, format, args...)
	}
}

// +---------------------------------------------------------------------------+
// | Getter for span of a tracing                                              |
// +---------------------------------------------------------------------------+

func (o *span) GetAttr() common.Attr        { return o.attr }
func (o *span) GetContext() context.Context { return o.ctx }
func (o *span) GetDuration() time.Duration  { return o.endTime.Sub(o.startTime) }
func (o *span) GetEndTime() time.Time       { return o.endTime }
func (o *span) GetLogs() []common.Item      { return o.logs }
func (o *span) GetName() string             { return o.name }
func (o *span) GetStartTime() time.Time     { return o.startTime }
func (o *span) GetTracing() common.Tracing  { return o.tracing }

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

func (o *span) acquire() *span {
	o.attr = make(common.Attr)
	o.startTime = time.Now()
	o.logs = make([]common.Item, 0)
	return o
}

func (o *span) log(level base.Level, format string, args ...any) {
	o.logs = append(o.logs, common.NewItem(o.ctx, level, fmt.Sprintf(format, args...)))
}

func (o *span) release() {
	o.attr, o.ctx, o.logs, o.tracing = nil, nil, nil, nil
	o.name = ""
	o.startTime, o.endTime = spanNilTime, spanNilTime
}

func (o *span) with(ctx context.Context) *span {
	if ctx == nil {
		o.ctx = context.WithValue(context.Background(), common.ContextKey, o.tracing)
	} else {
		o.ctx = context.WithValue(ctx, common.ContextKey, o.tracing)
	}
	return o
}
